<?php
/**
 * FastApp.
 * 1/5/25
 * @Author deepsea
 * @Contact (telegram:deepsea159)
 */

namespace App\Fastapp\Api\Service\Crypto;

use App\Fastapp\Api\Service\Account\WalletService;
use App\Fastapp\Api\Service\BaseService;
use App\Fastapp\Exception\TipException;
use App\Fastapp\Job\WalletAddressWithdrawalJob;
use App\Fastapp\Model\AccountWalletAddress;
use App\Fastapp\Model\AccountWalletAddressLog;
use App\Fastapp\Model\AccountWalletLog;
use App\Fastapp\Model\AccountWithdrawal;
use TronApi\TRX;
use Web3\Contract;
use Web3\Eth;
use Web3\Web3;

class PayService extends BaseService
{
    public function recharge(array $params): array
    {
        return [
            'address' => AccountWalletAddress::query()->where([
                'user_id' => get_uid(),
                'symbol' => self::getSymbol($params['contract']) ?: $params['symbol']
            ])->value('address')
        ];
    }

    public function log(?string $symbol): array
    {
        $map['user_id'] = get_uid();
        $query = AccountWalletAddressLog::query()->where($map);
        if ($symbol) {
            $symbol = strtoupper($symbol);
            $query->whereRaw("(symbol = '$symbol' or symbol_token='$symbol')");
        }
        $paginate = $query->orderByDesc('id')->simplePaginate();
        return ['list' => $paginate->items()];
    }

    public function withdrawal(array $params): array
    {
        $uid = get_uid();
        $symbol = $params['symbol'];
        $data = [
            'type' => AccountWalletLog::WITHDRAWAL,
            'user_id' => $uid,
            'symbol' => $symbol,
            'amount' => $params['amount'],
            'to' => $params['to_address']
        ];
        $currency = get_currency_cache($data['symbol']);
        if ($params['contract']) {
            $data['symbol_token'] = $symbol;
            if (get_currency_cache($symbol)['type'] != 3) throw new TipException(fastapp_t('fastapp.invalid_currency'));
            $data['symbol'] = self::getSymbol($params['contract']);
        }
        if (get_currency_cache($data['symbol'])['type'] != 2) throw new TipException(fastapp_t('fastapp.invalid_currency'));
        if ($data['symbol'] == 'TRX' && !(new \TronApi\Address($data['to']))->isValid()) throw new TipException(fastapp_t('fastapp.invalid_address'));
        if ($data['symbol'] == 'TON' && !\Olifanton\Interop\Address::isValid($data['to'])) throw new TipException(fastapp_t('fastapp.invalid_address'));
        if (in_array($data['symbol'], ['ETH', 'BNB']) && !\Web3\Validators\AddressValidator::validate($data['to'])) throw new TipException(fastapp_t('fastapp.invalid_address'));
        $data['from'] = AccountWalletAddress::query()->where(['user_id' => $uid, 'symbol' => $data['symbol']])->value('address');
        if (!$data['from']) throw new TipException(fastapp_t('fastapp.invalid_operation'));
        $minAmount = bcadd($currency['withdrawal_charge'], $currency['withdrawal_min'], $currency['trade_precision']);
        if (bccomp($data['amount'], $minAmount, $currency['trade_precision']) == -1) throw new TipException(fastapp_t('account.withdrawal_min_amount', ['amount' => $currency['withdrawal_min']]));
        $md = AccountWalletAddressLog::query()->create($data);
        if ($md->id) {
            $ojb = [
                'user_id' => $uid,
                'amount' => $params['amount'],
                'symbol' => $params['symbol'],
                'type' => AccountWalletLog::WITHDRAWAL,
                'object_type' => AccountWalletLog::OBJ_TYPE_WITHDRAWAL,
                'object_id' => $md->id
            ];
            try {
                $wallet = WalletService::dec($ojb);
                AccountWithdrawal::query()->create([
                    'user_id' => $uid,
                    'currency_id' => $wallet->currency_id,
                    'wallet_id' => $wallet->id,
                    'amount' => $params['amount'],
                    'charge' => $currency['withdrawal_charge'],
                    'pay_info_id' => $md->id,
                    'status' => 1,
                ]);
                fastapp_queue_push(new WalletAddressWithdrawalJob($md->id));
                return $md->toArray();
            } catch (\Throwable $throwable) {
                sys_notify($throwable->getMessage() . '|' . $throwable->getTraceAsString());
                $md->delete();
            }
        }
        throw new TipException('fasttapp.operate_fail');
    }

    public static function getSymbol(string|int $contract): string
    {
        if ($contract == 1) return 'TRX';
        if ($contract == 2) return 'BNB';
        if ($contract == 3) return 'ETH';
        if ($contract == 4) return 'TON';
        return '';
    }

    public static function getTronApi(array $option = []): TRX
    {
        $option = $option ?: [
            'headers' => [
                'TRON-PRO-API-KEY' => Config::TRX_API_KEY,
                'Content-Type' => 'application/json'
            ],
            'contract_address' => Config::ERC20_CONTRACT_ADDRESS_USDT,
            'decimals' => 6,
        ];
        return new TRX(Config::TRX_URL, $option);
    }

    public static function transTrx(AccountWalletAddress $fromAddress, string $toAddress, string $amount): bool|string
    {
        $transaction = self::getTronApi()->transfer($fromAddress->private_key,
            \TronApi\Support\Utils::addressToHex($fromAddress->address),
            \TronApi\Support\Utils::addressToHex($toAddress),
            floatval($amount));
        if ($transaction->txID) return $transaction->txID;
        return false;
    }

    public static function transTrxUsdt(AccountWalletAddress $fromAddress, string $toAddress, string $amount): bool|string
    {
        $transaction = self::getTronApi()->transferTRC20($fromAddress->private_key, $fromAddress->address, $toAddress, floatval($amount));
        if ($transaction->txID) {
            if ($fromAddress->nonce == strtolower(substr($transaction->txID, 2))) return false;
            $fromAddress->nonce = strtolower(substr($transaction->txID, 2));
            $fromAddress->save();
            return $transaction->txID;
        }
        return false;
    }

    public static function getETHApi(string $symbol): Web3
    {
        $url = '';
        if ($symbol == 'BNB') $url = Config::BNB_URL;
        if ($symbol == 'ETH') $url = Config::ETH_URL;
        if (!$url) throw new TipException(fastapp_t('fastapp.invalid_currency'));
        return new Web3('https://' . $url);
    }

    public static function transEth(AccountWalletAddress $fromAddress, string $toAddress, string $amount): string|bool
    {
        $web3 = self::getETHApi($fromAddress->symbol);
        $eth = $web3->getEth();
        $value = \Web3\Utils::toWei($amount, 'ether')->toHex();
        $nonce = null;
        $eth->getTransactionCount($fromAddress->address, function ($err, $count) use (&$nonce) {
            if ($err !== null) {
                throw new \Exception($err->getMessage());
            }
            $nonce = $count;
        });
        $gasPrice = self::getGasPrice($eth);
        $gasLimit = 21000; // 普通 ETH 转账所需的固定 gas
        $tx = [
            'nonce' => '0x' . dechex($nonce),
            'to' => $toAddress,
            'gas' => '0x' . dechex($gasLimit),
            'gasPrice' => '0x' . dechex($gasPrice),
            'value' => '0x' . $value,
            'data' => '0x',
        ];
        return self::getHash($tx, $fromAddress, $eth);
    }

    public static function transEthUsdt(AccountWalletAddress $fromAddress, string $toAddress, string $amount): string|bool
    {
        $web3 = self::getETHApi($fromAddress->symbol);
        $abi = '[{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"type":"function"}]';
        $decimals = 6; // USDT 通常为 6
        $amount = bcmul($amount, bcpow(10, $decimals), 0); // 转换为最小单位
        $eth = $web3->getEth();
        $contract = new Contract($web3->getProvider(), $abi);
        $contract->at(Config::ERC20_CONTRACT_ADDRESS_USDT);
        $data = $contract->getData('transfer', $toAddress, $amount);
        $nonce = null;
        $eth->getTransactionCount($fromAddress, 'pending', function ($err, $count) use (&$nonce) {
            if ($err !== null) throw new \Exception($err->getMessage());
            $nonce = $count;
        });
        $gasPrice = self::getGasPrice($eth);
        $gasLimit = 70000;
        $tx = [
            'nonce' => $nonce,
            'to' => Config::ERC20_CONTRACT_ADDRESS_USDT,
            'gas' => '0x' . dechex($gasLimit),
            'gasPrice' => '0x' . dechex($gasPrice),
            'value' => '0x0',
            'data' => $data
        ];
        return self::getHash($tx, $fromAddress, $eth);
    }

    public static function getGasPrice(Eth $eth): string
    {
        $gasPrice = null;
        $eth->gasPrice(function ($err, $price) use (&$gasPrice) {
            if ($err !== null) {
                throw new \Exception($err->getMessage());
            }
            $gasPrice = $price;
        });
        return $gasPrice;
    }

    public static function getHash(array $tx, AccountWalletAddress $fromAddress, Eth $eth): mixed
    {
        if ((int)$fromAddress->nonce >= $tx['nonce']) return false;
        $fromAddress->nonce = $tx['nonce'];
        $fromAddress->save();
        $tx['nonce'] = '0x' . dechex($tx['nonce']);
        $transaction = new \Web3p\EthereumTx\Transaction($tx);
        $signedTx = '0x' . $transaction->sign($fromAddress->private_key);
        $txHash = null;
        $eth->sendRawTransaction($signedTx, function ($err, $hash) use (&$txHash) {
            if ($err !== null) {
                throw new \Exception($err->getMessage());
            }
            $txHash = $hash;
        });
        return $txHash;
    }

}